"""User credit scoring module."""

from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple

import numpy as np

from ..data.data_loader import DataLoader
from ..features.feature_engineering import FeatureEngineer
from ..utils.logger import logger
from .config import SCORE_PARAMS, SCORE_RANGES
from .user_config import (ACTIVITY_BONUS, CREDIT_LIMITS, CREDIT_WEIGHTS,
                         PENALTY_POINTS, RECOVERY_REQUIREMENTS, RISK_THRESHOLDS,
                         SCORE_ADJUSTMENTS, SCORE_DECAY, USER_WEIGHTS)


class UserCreditScorer:
    """User credit scoring class."""
    
    def __init__(self):
        """Initialize the UserCreditScorer."""
        self.data_loader = DataLoader()
        self.feature_engineer = FeatureEngineer()
    
    def calculate_user_score(
        self,
        user_id: str
    ) -> Tuple[float, Dict[str, float], str, Dict[str, any]]:
        """
        Calculate credit score for a user.
        
        Args:
            user_id: The ID of the user.
            
        Returns:
            Tuple[float, Dict[str, float], str, Dict[str, any]]:
                Overall score, dimension scores, credit level, and credit limits.
        """
        try:
            # Load user data
            raw_features = self.data_loader.load_user_data(user_id)
            
            if not raw_features:
                logger.warning(f"No data available for user {user_id}")
                return SCORE_PARAMS["base_score"], {}, "B", CREDIT_LIMITS["B"]
            
            # Process features
            processed_features = self.feature_engineer.process_user_features(
                raw_features
            )
            
            # Normalize features
            normalized_features = self.feature_engineer.normalize_features(
                processed_features
            )
            
            # Calculate weighted score
            overall_score, dimension_scores = self.feature_engineer.calculate_weighted_score(
                normalized_features,
                USER_WEIGHTS
            )
            
            # Apply adjustments
            adjusted_score = self._apply_score_adjustments(
                user_id,
                overall_score
            )
            
            # Scale to final score range
            final_score = self._scale_score(adjusted_score)
            
            # Determine credit level and limits
            credit_level = self._determine_credit_level(final_score)
            credit_limits = self._get_credit_limits(credit_level)
            
            # Save score
            self._save_user_score(
                user_id=user_id,
                score=final_score,
                dimension_scores=dimension_scores
            )
            
            return final_score, dimension_scores, credit_level, credit_limits
            
        except Exception as e:
            logger.error(f"Error calculating user score: {e}")
            raise
    
    def get_user_score_history(
        self,
        user_id: str,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None
    ) -> List[Dict]:
        """
        Get historical credit scores for a user.
        
        Args:
            user_id: The ID of the user.
            start_date: Optional start date for history.
            end_date: Optional end date for history.
            
        Returns:
            List[Dict]: List of historical scores with details.
        """
        try:
            query = """
            SELECT
                score,
                score_details,
                created_at
            FROM credit_scores
            WHERE
                entity_id = :user_id
                AND entity_type = 'user'
            """
            
            params = {"user_id": user_id}
            
            if start_date:
                query += " AND created_at >= :start_date"
                params["start_date"] = start_date
                
            if end_date:
                query += " AND created_at <= :end_date"
                params["end_date"] = end_date
                
            query += " ORDER BY created_at DESC"
            
            with self.data_loader.get_engine().connect() as conn:
                result = conn.execute(query, params).fetchall()
            
            return [
                {
                    "score": row[0],
                    "details": row[1],
                    "timestamp": row[2].isoformat()
                }
                for row in result
            ]
            
        except Exception as e:
            logger.error(f"Error getting user score history: {e}")
            raise
    
    def check_credit_recovery_eligibility(
        self,
        user_id: str
    ) -> Tuple[bool, Dict[str, any]]:
        """
        Check if user is eligible for credit recovery.
        
        Args:
            user_id: The ID of the user.
            
        Returns:
            Tuple[bool, Dict[str, any]]: Eligibility status and requirements.
        """
        try:
            # Get current credit level
            score, _, level, _ = self.calculate_user_score(user_id)
            
            if level not in RECOVERY_REQUIREMENTS:
                return False, {"reason": "Current level not eligible for recovery"}
            
            requirements = RECOVERY_REQUIREMENTS[level]
            
            # Check recent order history
            recent_orders = self._get_recent_orders(
                user_id,
                days=requirements["clean_period"]
            )
            
            good_orders = len([
                order for order in recent_orders
                if self._is_good_order(order)
            ])
            
            total_amount = sum(order["amount"] for order in recent_orders)
            
            # Check eligibility
            is_eligible = (
                good_orders >= requirements["min_good_orders"]
                and total_amount >= requirements["min_amount"]
            )
            
            status = {
                "current_level": level,
                "required_orders": requirements["min_good_orders"],
                "completed_orders": good_orders,
                "required_amount": requirements["min_amount"],
                "current_amount": total_amount,
                "clean_period": requirements["clean_period"],
                "is_eligible": is_eligible
            }
            
            return is_eligible, status
            
        except Exception as e:
            logger.error(f"Error checking credit recovery eligibility: {e}")
            raise
    
    def process_credit_recovery(
        self,
        user_id: str
    ) -> Tuple[bool, str, Optional[str]]:
        """
        Process credit recovery for eligible user.
        
        Args:
            user_id: The ID of the user.
            
        Returns:
            Tuple[bool, str, Optional[str]]:
                Success status, message, and new credit level if successful.
        """
        try:
            # Check eligibility
            is_eligible, status = self.check_credit_recovery_eligibility(user_id)
            
            if not is_eligible:
                return False, "Not eligible for credit recovery", None
            
            # Calculate new score with bonus
            current_score, dimensions, level, _ = self.calculate_user_score(user_id)
            
            # Add recovery bonus
            recovery_bonus = 50  # Points to add for recovery
            new_score = min(
                current_score + recovery_bonus,
                SCORE_PARAMS["max_score"]
            )
            
            # Save new score
            self._save_user_score(
                user_id=user_id,
                score=new_score,
                dimension_scores=dimensions
            )
            
            # Get new level
            new_level = self._determine_credit_level(new_score)
            
            return True, "Credit recovery successful", new_level
            
        except Exception as e:
            logger.error(f"Error processing credit recovery: {e}")
            raise
    
    def update_user_scores(self, batch_size: int = 1000) -> int:
        """
        Update scores for all users in batches.
        
        Args:
            batch_size: Number of users to process in each batch.
            
        Returns:
            int: Number of users processed.
        """
        try:
            # Get all user IDs
            query = "SELECT DISTINCT user_id FROM users"
            
            with self.data_loader.get_engine().connect() as conn:
                user_ids = [row[0] for row in conn.execute(query).fetchall()]
            
            processed_count = 0
            for i in range(0, len(user_ids), batch_size):
                batch = user_ids[i:i + batch_size]
                
                for user_id in batch:
                    try:
                        self.calculate_user_score(user_id)
                        processed_count += 1
                    except Exception as e:
                        logger.error(f"Error updating score for user {user_id}: {e}")
                        continue
            
            return processed_count
            
        except Exception as e:
            logger.error(f"Error in batch score update: {e}")
            raise
    
    def _scale_score(self, raw_score: float) -> float:
        """Scale raw score to final score range."""
        min_score = SCORE_PARAMS["min_score"]
        max_score = SCORE_PARAMS["max_score"]
        
        # Apply sigmoid transformation
        sigmoid = 1 / (1 + np.exp(-10 * (raw_score - 0.5)))
        
        # Scale to final range
        final_score = min_score + sigmoid * (max_score - min_score)
        
        return round(final_score, 2)
    
    def _determine_credit_level(self, score: float) -> str:
        """Determine credit level based on score."""
        for min_score, max_score, level in SCORE_RANGES:
            if min_score <= score <= max_score:
                return level
        return "D"  # Default to lowest level
    
    def _get_credit_limits(self, credit_level: str) -> Dict[str, float]:
        """Get credit limits for user credit level."""
        return CREDIT_LIMITS.get(credit_level, CREDIT_LIMITS["D"])
    
    def _apply_score_adjustments(
        self,
        user_id: str,
        base_score: float
    ) -> float:
        """Apply various adjustments to the base score."""
        try:
            adjustments = 0.0
            
            # Get recent activities
            activities = self._get_recent_activities(user_id)
            
            # Apply activity bonuses
            for activity in activities:
                bonus = ACTIVITY_BONUS.get(activity["type"], 0)
                adjustments += bonus
            
            # Apply penalties
            penalties = self._get_recent_penalties(user_id)
            for penalty in penalties:
                points = PENALTY_POINTS.get(penalty["type"], 0)
                adjustments += points
            
            # Apply score decay
            decay = self._calculate_score_decay(user_id)
            
            # Combine adjustments
            adjusted_score = base_score + adjustments - decay
            
            # Ensure score stays within bounds
            return max(
                min(adjusted_score, SCORE_PARAMS["max_score"]),
                SCORE_PARAMS["min_score"]
            )
            
        except Exception as e:
            logger.error(f"Error applying score adjustments: {e}")
            return base_score
    
    def _save_user_score(
        self,
        user_id: str,
        score: float,
        dimension_scores: Dict[str, float]
    ) -> bool:
        """Save user score to database."""
        return self.data_loader.save_score(
            entity_id=user_id,
            entity_type="user",
            score=score,
            score_details=dimension_scores
        )
    
    def _get_recent_orders(
        self,
        user_id: str,
        days: int
    ) -> List[Dict]:
        """Get recent orders for user."""
        try:
            start_date = (datetime.now() - timedelta(days=days)).isoformat()
            
            query = """
            SELECT
                order_id,
                amount,
                status,
                created_at
            FROM orders
            WHERE
                user_id = :user_id
                AND created_at >= :start_date
            """
            
            with self.data_loader.get_engine().connect() as conn:
                result = conn.execute(
                    query,
                    {
                        "user_id": user_id,
                        "start_date": start_date
                    }
                ).fetchall()
            
            return [
                {
                    "order_id": row[0],
                    "amount": row[1],
                    "status": row[2],
                    "created_at": row[3]
                }
                for row in result
            ]
            
        except Exception as e:
            logger.error(f"Error getting recent orders: {e}")
            return []
    
    def _is_good_order(self, order: Dict) -> bool:
        """Check if order is considered 'good' for credit recovery."""
        return (
            order["status"] == "completed"
            and order["amount"] > 0
        )
    
    def _get_recent_activities(
        self,
        user_id: str,
        days: int = 30
    ) -> List[Dict]:
        """Get recent user activities."""
        try:
            start_date = (datetime.now() - timedelta(days=days)).isoformat()
            
            query = """
            SELECT
                activity_type,
                created_at
            FROM user_activities
            WHERE
                user_id = :user_id
                AND created_at >= :start_date
            """
            
            with self.data_loader.get_engine().connect() as conn:
                result = conn.execute(
                    query,
                    {
                        "user_id": user_id,
                        "start_date": start_date
                    }
                ).fetchall()
            
            return [
                {
                    "type": row[0],
                    "timestamp": row[1]
                }
                for row in result
            ]
            
        except Exception as e:
            logger.error(f"Error getting recent activities: {e}")
            return []
    
    def _get_recent_penalties(
        self,
        user_id: str,
        days: int = 90
    ) -> List[Dict]:
        """Get recent penalties for user."""
        try:
            start_date = (datetime.now() - timedelta(days=days)).isoformat()
            
            query = """
            SELECT
                penalty_type,
                created_at
            FROM user_penalties
            WHERE
                user_id = :user_id
                AND created_at >= :start_date
            """
            
            with self.data_loader.get_engine().connect() as conn:
                result = conn.execute(
                    query,
                    {
                        "user_id": user_id,
                        "start_date": start_date
                    }
                ).fetchall()
            
            return [
                {
                    "type": row[0],
                    "timestamp": row[1]
                }
                for row in result
            ]
            
        except Exception as e:
            logger.error(f"Error getting recent penalties: {e}")
            return []
    
    def _calculate_score_decay(self, user_id: str) -> float:
        """Calculate score decay based on activity."""
        try:
            # Get last activity date
            query = """
            SELECT MAX(created_at)
            FROM user_activities
            WHERE user_id = :user_id
            """
            
            with self.data_loader.get_engine().connect() as conn:
                last_activity = conn.execute(
                    query,
                    {"user_id": user_id}
                ).scalar()
            
            if not last_activity:
                return 0
            
            # Calculate days since last activity
            days_inactive = (datetime.now() - last_activity).days
            
            if days_inactive <= 0:
                return 0
            
            # Calculate decay
            if days_inactive > 30:  # Inactive account
                decay = days_inactive * SCORE_DECAY["inactive_rate"]
            else:
                decay = days_inactive * SCORE_DECAY["daily_rate"]
            
            return min(decay, SCORE_DECAY["minimum_score"])
            
        except Exception as e:
            logger.error(f"Error calculating score decay: {e}")
            return 0